﻿using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Devonline.Security;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using HashAlgorithm = Devonline.Core.HashAlgorithm;

namespace Devonline.AspNetCore.Security;

/// <summary>
/// 使用数据库上下文存储的数据安全服务
/// 证书和密钥的存储会固定使用 Certificate 和 Secret 相关的对象
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
public class DataSecurityService<TDbContext>(
    ILogger<DataSecurityService<TDbContext>> logger,
    DataSecuritySetting securitySetting,
    TDbContext context) :
    DataSecurityService(securitySetting),
    IDataSecurityService<TDbContext>,
    IDataSecurityService,
    ISecurityService
    where TDbContext : BaseSecurityDbContext
{
    protected readonly ILogger<DataSecurityService<TDbContext>> _logger = logger;
    protected readonly DataSecuritySetting _dataSecuritySetting = securitySetting;
    protected readonly TDbContext _dbContext = context;
    protected readonly DbSet<Certificate> _certificates = context.Certificates;
    protected readonly DbSet<Secret> _secrets = context.Secrets;

    /// <summary>
    /// 身份标识
    /// </summary>
    private string _identityId = string.Empty;
    /// <summary>
    /// 身份类型
    /// </summary>
    private IdentityType _identityType = IdentityType.User;

    /// <summary>
    /// 重载的根据密钥编号获取密钥的方法
    /// 此过程从数据库查询密钥信息, 并使用证书解密密钥
    /// </summary>
    /// <param name="keyId">密钥编号</param>
    /// <returns>Aes 密钥信息</returns>
    protected virtual async Task<KeyIV> GetKeyIVAsync(long keyId)
    {
        var keyIV = _symmetricSecurityService.GetKeyIV(keyId);
        if (keyIV is not null)
        {
            // keyIV 存在直接返回
            return keyIV;
        }

        keyIV = await GetFromDatabaseAsync(keyId.ToString());
        _symmetricSecurityService.SetKeyIV(keyId, keyIV);
        return keyIV;
    }
    /// <summary>
    /// 重载的获取或创建密钥的方法
    /// </summary>
    /// <returns></returns>
    protected virtual async Task<KeyIV> GetKeyIVAsync()
    {
        var keyIV = _symmetricSecurityService.GetKeyIV();
        if (keyIV!.UsedCount == 0)
        {
            //此时的 _lastKeyIV 是新创建的, 需要保存到数据库
            await GetOrCreateSecretAsync(keyIV);
        }

        return keyIV;
    }
    /// <summary>
    /// 获取文件绝对地址
    /// </summary>
    /// <param name="fileName">文件名</param>
    /// <returns></returns>
    protected virtual string GetFilePath(string fileName) => Path.Combine(_dataSecuritySetting.RootPath, fileName);

    /// <summary>
    /// keyIV 不存在时从数据库查询
    /// </summary>
    /// <param name="keyId">密钥编号</param>
    /// <returns></returns>
    /// <exception cref="KeyNotFoundException"></exception>
    /// <exception cref="Exception"></exception>
    protected virtual async Task<KeyIV> GetFromDatabaseAsync(string keyId)
    {
        // keyIV 不存在则从数据库查询
        var secret = await _secrets.FirstOrDefaultAsync(x => x.Id == keyId) ?? throw new KeyNotFoundException("Data security service get Aes Key IV secret not found!");
        var certificate = await _certificates.FirstOrDefaultAsync(x => x.Id == secret.CertificateId) ?? throw new KeyNotFoundException("Data security service get Aes Key IV certificate not found!");

        //证书的私钥保存在证书内容中, 解密时需要证书存在私钥
        if (string.IsNullOrWhiteSpace(certificate.Content))
        {
            throw new Exception("Data security service get Aes Key IV the certificate has no content!");
        }

        //使用数据库中的对象构造证书
        var x509Certificate2 = GetX509Certificate2(certificate);

        //使用证书解密密钥
        var value = await _certificateSecurityService.DecryptAsync(x509Certificate2, Convert.FromBase64String(secret.Content)) ?? throw new Exception("Data security service get Aes Key IV decrypt secret key failed!");

        //构造新的密钥
        return new KeyIV(value);
    }
    /// <summary>
    /// 获取或创建最新的用户密钥
    /// </summary>
    /// <param name="keyIV">密钥和初始化向量</param>
    /// <returns></returns>
    protected virtual async Task<Secret> GetOrCreateSecretAsync(KeyIV keyIV)
    {
        // keyIV 不存在则从数据库查询
        var keyId = keyIV.Id.ToString();
        var secret = await _secrets.FirstOrDefaultAsync(x => x.Id == keyId);
        if (secret is not null)
        {
            return secret;
        }

        var certificate = await GetOrCreateCertificateAsync();
        var x509Certificate2 = GetX509Certificate2(certificate);
        var aesKey = await _certificateSecurityService.EncryptAsync(x509Certificate2, keyIV.Value);
        secret = new Secret
        {
            Id = keyIV.Id.ToString(),
            IdentityType = _identityType,
            OwnerId = _identityId,
            CertificateId = certificate.Id,
            CipherMode = _securitySetting.CipherMode,
            KeySize = _securitySetting.KeySize,
            HashAlgorithm = HashAlgorithm.SHA256,
            EncryptionAlgorithm = Core.SymmetricAlgorithm.AES_256_CBC,
            Start = DateTime.UtcNow,
            End = DateTime.UtcNow.AddMinutes(_securitySetting.ExpireTime),
            HashCode = Convert.ToBase64String(keyIV.Value),
            Content = Convert.ToBase64String(aesKey)
        };

        secret.Create(_identityId);
        _secrets.Add(secret);
        await _dbContext.SaveChangesAsync();
        return secret;
    }
    /// <summary>
    /// 获取或创建最新的用户证书
    /// </summary>
    /// <returns></returns>
    protected virtual async Task<Certificate> GetOrCreateCertificateAsync()
    {
        var certificate = await _certificates.Where(x => x.IdentityType == _identityType && x.OwnerId == _identityId && x.End != null && x.End.Value > DateTime.UtcNow).OrderBy(x => x.Start).LastOrDefaultAsync();
        if (certificate is null)
        {
            //用户证书尚不存在时创建
            certificate = new Certificate
            {
                IdentityType = _identityType,
                OwnerId = _identityId,
                Start = DateTime.UtcNow,
                End = DateTime.UtcNow.AddMonths(_dataSecuritySetting.CertificateExpireTime),
                HashAlgorithm = HashAlgorithm.SHA256,
                EncryptionAlgorithm = Core.SymmetricAlgorithm.AES_256_CBC,
                SignatureAlgorithm = EncryptionAlgorithm.RSA,
                KeySize = _dataSecuritySetting.RSAKeySize,
            };

            certificate.Create(_identityId);
            var fileName = _identityId + CHAR_HLINE + certificate.Id + DEFAULT_EXCEL_FILE_EXTENSION_CERTIFICATE;
            certificate.Path = GetFilePath(fileName);
            var certificateInfo = GetDefaultCertificateInfo();
            certificateInfo.Password = certificate.Id.GetHashString();
            certificateInfo.FilePath = certificate.Path.GetAbsolutePath();
            var x509Certificate2 = await _certificateSecurityService.GenerateAsync(certificateInfo);
            certificate.Password = certificateInfo.Password;
            var publicKey = x509Certificate2.GetRSAPublicKey()!.ExportRSAPublicKey();
            certificate.PublicKey = Convert.ToBase64String(publicKey);
            certificate.HashCode = Convert.ToBase64String(SHA256.HashData(publicKey));
            certificate.Content = Convert.ToBase64String(x509Certificate2.RawData);
            await _dbContext.SaveChangesAsync();
        }

        return certificate;
    }
    /// <summary>
    /// 根据 Certificate 数据记录还原证书
    /// </summary>
    /// <param name="certificate"></param>
    /// <returns></returns>
    protected virtual X509Certificate2 GetX509Certificate2(Certificate certificate)
    {
        //如果存在证书路径和密码, 则直接创建完整证书
        if (!string.IsNullOrWhiteSpace(certificate.Path))
        {
            return X509CertificateLoader.LoadPkcs12FromFile(certificate.Path, certificate.Password);
        }

        //如果存在证书内容, 则按内容创建证书, 可能仅包含公钥或私钥
        if (!string.IsNullOrWhiteSpace(certificate.Content))
        {
            return X509CertificateLoader.LoadPkcs12(Convert.FromBase64String(certificate.Content), certificate.Password);
        }

        //如果存在公钥, 则按公钥创建证书
        return X509CertificateLoader.LoadCertificate(Convert.FromBase64String(certificate.PublicKey));
    }
    /// <summary>
    /// 根据配置文件获取默认证书信息
    /// </summary>
    /// <returns></returns>
    protected virtual X509CertificateInfo GetDefaultCertificateInfo()
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(_dataSecuritySetting.SignatureName);
        return new X509CertificateInfo(_dataSecuritySetting.SignatureName)
        {
            KeySize = _dataSecuritySetting.RSAKeySize,
            ContentType = _dataSecuritySetting.ContentType,
            Critical = _dataSecuritySetting.Critical,
            ExpireTime = _dataSecuritySetting.CertificateExpireTime,
            HashAlgorithmName = _dataSecuritySetting.HashAlgorithmName,
            KeyUsageFlags = _dataSecuritySetting.KeyUsageFlags,
            Password = _dataSecuritySetting.Password,
            SignaturePadding = _dataSecuritySetting.SignaturePadding == RSASignaturePaddingMode.Pkcs1 ? RSASignaturePadding.Pkcs1 : RSASignaturePadding.Pss
        };
    }

    /// <summary>
    /// 设定当前使用者的身份
    /// </summary>
    /// <param name="identityId">身份标识</param>
    /// <param name="identityType">身份类型</param>
    public virtual void SetIdentity(string identityId, IdentityType identityType = IdentityType.User) => (_identityId, _identityType) = (identityId, identityType);
    /// <summary>
    /// 重载的字符串加密算法, 在针对数据字段的加密中, 使用和创建 Certificate 和 Secret 以保存密钥, 加密后的数据密文仅保存 Aes 密钥的编号和加密后的字符串
    /// </summary>
    /// <param name="plainText">原始数据明文文本</param>
    /// <returns>加密后的包含 Aes 密钥编号的密文文本 Base64 编码字符串</returns>
    public virtual async Task<string> EncryptAsync(string plainText)
    {
        //1. 获取 Aes 密钥
        var keyIV = await GetKeyIVAsync();
        //2. 将数据转换为字节流
        var data = _securitySetting.Encoding.GetBytes(plainText);
        //3. 加密数据
        var encrypted = await _symmetricSecurityService.EncryptAsync(keyIV.Key, keyIV.IV, data);
        //4. 标记密钥使用次数
        keyIV.Used();
        //5. 构造带密钥编号的数据密文
        var keyData = new KeyData(keyIV.Id, encrypted);
        //6. 返回 Base64 编码的密文字符串
        return Convert.ToBase64String(keyData.Value);
    }
    /// <summary>
    /// 重载的字符串解密算法, 在针对数据字段的解密中, 数据密文中包含了 Aes 密钥编号和原始数据密文, 将使用 Certificate 和 Secret 中已存在的密钥, 解密数据密文
    /// </summary>
    /// <param name="cipherText">加密后的包含 Aes 密钥编号的密文文本 Base64 编码字符串</param>
    /// <returns>解密后的原始数据明文文本</returns>
    public virtual async Task<string> DecryptAsync(string cipherText)
    {
        //1. 将 Base64 编码的密文字符串转换为字节流
        var data = Convert.FromBase64String(cipherText);
        //2. 构造带密钥编号的数据密文
        var keyData = new KeyData(data);
        //3. 从带密钥编号的数据密文中获取密钥
        var keyIV = await GetKeyIVAsync(keyData.KeyId);
        //4. 解密数据密文
        var decrypted = await _symmetricSecurityService.DecryptAsync(keyIV.Key, keyIV.IV, keyData.Value);
        //5. 返回源数据明文字符串
        return _securitySetting.Encoding.GetString(decrypted);
    }
}